package com.yupi.shortchain.aop;


import cn.dev33.satoken.stp.StpUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yupi.shortchain.annotation.BlackListInterceptor;
import com.yupi.shortchain.common.ErrorCode;
import com.yupi.shortchain.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RMapCache;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;


@Aspect
@Component
@Slf4j
public class RateLimitInterceptor {

    @Resource
    private Redisson redisson;

    private RMapCache<String, Long> blacklist;
    // 用来存储用户ID与对应的RateLimiter对象
    private final Cache<String, RRateLimiter> userRateLimiters = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();

    //切入点
    @Pointcut("@annotation(com.yupi.shortchain.annotation.BlackListInterceptor)")
    public void addPoint(){}

    // 环绕通知
    @Around("addPoint() && @annotation(blackListInterceptor)")
    public Object around(ProceedingJoinPoint joinPoint, BlackListInterceptor blackListInterceptor ) throws Throwable {
        // 获取拦截标识
        String key = blackListInterceptor.key();

        // 获取请求路径
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        //获取 IP
        String remoteHost = httpServletRequest.getRemoteHost();

        if(StringUtils.isBlank(key)){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"拦截的key不能为空");
        }
        // 获取拦截字段
        String keyTarget;
        if("default".equals(key)){
            keyTarget = "systemUid" + StpUtil.getLoginId().toString();
        }else{
            keyTarget = getAttrValue(key, joinPoint.getArgs());
        }
        log.debug("拦截标识：{}", keyTarget);

        //启动黑名单拦截
        // 黑名单拦截
//        if (blackListInterceptor.protectLimit() != 0 && null != blacklist.getOrDefault(keyTarget, null) && (blacklist.getOrDefault(keyTarget, 0L) > blackListInterceptor.protectLimit()
//                ||blacklist.getOrDefault(remoteHost, 0L) > blackListInterceptor.protectLimit())) {
//            log.info("有小黑子被我抓住了！给他 24 小时封禁套餐吧：{}", keyTarget);
//            return fallbackMethodResult(joinPoint, blackListInterceptor.fallbackMethod());
//        }
        // 设置限流
        RRateLimiter rateLimiter;
        if (!userRateLimiters.asMap().containsKey(keyTarget)) {
            rateLimiter = redisson.getRateLimiter(keyTarget);
            // 设置RateLimiter的速率，每秒发放10个令牌
            rateLimiter.trySetRate(RateType.OVERALL, blackListInterceptor.rateLimit(), 1, RateIntervalUnit.SECONDS);
            userRateLimiters.put(keyTarget, rateLimiter);
        } else {
            rateLimiter = userRateLimiters.getIfPresent(keyTarget);
        }
        // 限流拦截
        if (rateLimiter != null && !rateLimiter.tryAcquire()) {
            if (blackListInterceptor.protectLimit() != 0) {
                //封标识
                blacklist.put(keyTarget, blacklist.getOrDefault(keyTarget, 0L) + 1L);
                //封 IP
                blacklist.put(remoteHost, blacklist.getOrDefault(remoteHost, 0L) + 1L);
            }
            log.info("你刷这么快干嘛黑子：{}", keyTarget);
            return fallbackMethodResult(joinPoint, blackListInterceptor.fallbackMethod());
        }

        // 返回结果
        return joinPoint.proceed();
    }
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }




    /**
     * 实际根据自身业务调整，主要是为了获取通过某个值做拦截
     *
     */
    public String getAttrValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            // 第一个请求参数为字符串，则将其作为 keyTarget
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr：{}", attr, e);
            }
        }
        return filedValue;
    }

    /**
     * 获取对象的特定属性值
     *
     * @param item 对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }
    /**
     * 根据名称获取方法，该方法同时兼顾继承类获取父类的属性
     *
     * @param item 对象
     * @param name 属性名
     * @return 该属性对应方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }
}
